package com.jnc.core.config.shiro;

import at.pollux.thymeleaf.shiro.dialect.ShiroDialect;
import com.jnc.common.constant.ShiroConstant;
import com.jnc.core.config.redis.RedisStore;
import com.jnc.core.config.shiro.filter.SyncSessionFilter;
import com.jnc.core.config.shiro.realm.UserRealm;
import com.jnc.core.config.shiro.session.RedisShiroCacheManager;
import com.jnc.core.config.shiro.session.RedisShiroSessionDAO;
import com.jnc.core.config.shiro.session.RedisShiroSessionManager;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.codec.Base64;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.mgt.SessionManager;
import org.apache.shiro.spring.LifecycleBeanPostProcessor;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;

/**
 * @Author: jjn
 * @Date: 2018/9/21
 * @Desc: Shiro配置类
 */
@Configuration
public class ShiroConfig {

    @Autowired
    private RedisStore redisStore;

    /**
     * 自定义Realm
     * @return
     */
    @Bean
    public UserRealm createUserRealm(){
        UserRealm userRealm = new UserRealm();
        userRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return userRealm;
    }

    /**
     * 自定义SessionDAO
     * @return
     */
    public RedisShiroSessionDAO createRedisShiroSessionDAO(RedisStore redisStore){
        //注入CacheManager对象实例，操作redis
        RedisShiroSessionDAO sessionDAO = new RedisShiroSessionDAO(redisStore);
        //设置缓存管理器
        sessionDAO.setCacheManager(createCacheManager(redisStore));
        return sessionDAO;
    }

    /**
     * 自定义shiro session cookie
     * @return
     */
    public SimpleCookie createSessionIdCookie(){
        SimpleCookie simpleCookie = new SimpleCookie(ShiroConstant.SESSIONID_COOKIE_NAME);
        //设置HttpOnly属性，js脚本将无法读取到cookie信息，这样能有效的防止XSS攻击
        simpleCookie.setHttpOnly(true);
        simpleCookie.setPath("/");
        //定义Cookie的过期时间，单位为秒，如果设置为-1表示浏览器关闭，则Cookie消失
        simpleCookie.setMaxAge(-1);
        return simpleCookie;
    }

    /**
     * 自定义sessionManager
     * @return
     */
    public SessionManager createSessionManager(RedisStore redisStore){
        RedisShiroSessionManager sessionManager = new RedisShiroSessionManager(redisStore);
        //自定义sessionDAO
        sessionManager.setSessionDAO(createRedisShiroSessionDAO(redisStore));
        //session失效时长，单位是毫秒
        sessionManager.setGlobalSessionTimeout(ShiroConstant.GLOBAL_SESSION_TIMEOUT);
        //删除失效的session
        sessionManager.setDeleteInvalidSessions(true);
        // 去掉 JSESSIONID
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        //所有的session一定要将id设置到Cookie之中，需要提供有Cookie的操作模板
        sessionManager.setSessionIdCookie(createSessionIdCookie());
        //定义sessionIdCookie模板可以进行操作的启用
        sessionManager.setSessionIdCookieEnabled(true);
        return sessionManager;
    }

    /**
     * 记住我cookie
     * @return
     */
    public SimpleCookie createRemeberMeCookie(){
        SimpleCookie simpleCookie = new SimpleCookie(ShiroConstant.REMEBER_ME_COOKIE_NAME);
        //设置HttpOnly属性，js脚本将无法读取到cookie信息，这样能有效的防止XSS攻击
        simpleCookie.setHttpOnly(true);
        //定义Cookie的过期时间，单位为秒，如果设置为-1表示浏览器关闭，则Cookie消失
        simpleCookie.setMaxAge(2592000);
        return simpleCookie;
    }

    /**
     * 自定义记住我管理器
     * @return
     */
    public CookieRememberMeManager createRememberMeManager(){
        CookieRememberMeManager cookieRememberMeManager = new CookieRememberMeManager();
        //设置记住我的cookie
        cookieRememberMeManager.setCookie(createRemeberMeCookie());
        //避免rememberMe，登录界面报:org.apache.shiro.crypto.CryptoException
        cookieRememberMeManager.setCipherKey(Base64.decode("6ZmI6I2j5Y+R5aSn5ZOlAA=="));
        return cookieRememberMeManager;
    }

    /**
     * 自定义缓存管理器
     * @return
     */
    public RedisShiroCacheManager createCacheManager(RedisStore redisStore){
        RedisShiroCacheManager redisShiroCacheManager = new RedisShiroCacheManager(redisStore);
        return redisShiroCacheManager;
    }

    /**
     * 自定义安全管理器
     * @return
     */
    public SecurityManager createSecurityManager(RedisStore redisStore){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //自定义realm
        securityManager.setRealm(createUserRealm());
        //自定义sessionManager
        securityManager.setSessionManager(createSessionManager(redisStore));
        //自定义rememberManager
        securityManager.setRememberMeManager(createRememberMeManager());
        //自定义cacheManager
        securityManager.setCacheManager(createCacheManager(redisStore));
        return securityManager;
    }

    /**
     * Shiro过滤器配置
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean createShiroFilter(RedisStore redisStore){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        // Shiro的核心安全接口,这个属性是必须的
        shiroFilterFactoryBean.setSecurityManager(createSecurityManager(redisStore));
        // 身份认证失败，则跳转到登录页面的配置
        shiroFilterFactoryBean.setLoginUrl(ShiroConstant.LOGIN_URL);
        // 权限认证失败，则跳转到指定页面
        shiroFilterFactoryBean.setUnauthorizedUrl(ShiroConstant.UNAUTH_URL);
        // Shiro连接约束配置，即过滤链的定义
        LinkedHashMap<String, String> filterMap = new LinkedHashMap<>();
        //对静态资源设置匿名访问
        filterMap.put("/css/**", "anon");
        filterMap.put("/img/**", "anon");
        filterMap.put("/js/**", "anon");
        filterMap.put("/plugin/**", "anon");
        filterMap.put("/script/**", "anon");
        filterMap.put("/favicon.ico", "anon");
        // 退出 logout地址，shiro去清除session
        filterMap.put("/logout", "logout");
        // 不需要拦截的访问
        filterMap.put("/login", "anon");
        filterMap.put("/login.html", "anon");
        filterMap.put("/", "anon");
        //需要拦截认证的
        filterMap.put("/**", "authc");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterMap);
        return shiroFilterFactoryBean;
    }

    /**
     * 凭证匹配器
     * （由于我们的密码校验交给Shiro的SimpleAuthenticationInfo进行处理了
     *  所以我们需要修改下doGetAuthenticationInfo中的代码;
     * ）
     * @return
     */
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        hashedCredentialsMatcher.setHashAlgorithmName(ShiroConstant.HASH_ENCRYPT_TYPE);//散列算法:这里使用MD5算法;
        hashedCredentialsMatcher.setHashIterations(ShiroConstant.HASH_ENCRYPT_NUM);//散列的次数，比如散列两次，相当于 md5(md5(""));
        //指定Hash散列值使用Hex加密存储。value="false"表明hash散列值用用Base64-encoded存储
        //去掉RememberMe功能，需要去掉这个配置，否则启动到登录界面会报错
        hashedCredentialsMatcher.setStoredCredentialsHexEncoded(true);
        return hashedCredentialsMatcher;
    }

    /**
     * 自定义Session同步过滤器
     * @return
     */
    @Bean
    public SyncSessionFilter syncSessionFilter(RedisStore redisStore){
        SyncSessionFilter syncSessionFilter = new SyncSessionFilter(redisStore);
        return syncSessionFilter;
    }

    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    /**
     * thymeleaf模板引擎和shiro框架的整合，为了在thymeleaf里使用shiro的标签
     */
    @Bean
    public ShiroDialect shiroDialect()
    {
        return new ShiroDialect();
    }

    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
        proxyCreator.setProxyTargetClass(true);
        return proxyCreator;
    }

    /**
     * 开启Shiro注解通知器
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(RedisStore redisStore){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(createSecurityManager(redisStore));
        return authorizationAttributeSourceAdvisor;
    }

}
